package vip.liux.backend.application.serviceImpl.pay;


import jakarta.annotation.Resource;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.collections4.CollectionUtils;
import org.apache.commons.lang3.ObjectUtils;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import vip.liux.application.dots.PagedResultDto;
import vip.liux.backend.application.service.pay.*;
import vip.liux.backend.application.service.pay.dto.app.PayAppDto;
import vip.liux.backend.application.service.pay.dto.channel.PayChannelDto;
import vip.liux.backend.application.service.pay.dto.transfer.PayTransferCreateDto;
import vip.liux.backend.application.service.pay.dto.transfer.PayTransferDto;
import vip.liux.backend.application.service.pay.dto.transfer.PayTransferQueryDto;
import vip.liux.backend.infrastructure.code.BizCodeGenerator;
import vip.liux.contracts.infrastructure.pay.PayClient;
import vip.liux.contracts.infrastructure.pay.dto.transfer.ClientPayTransferDto;
import vip.liux.contracts.infrastructure.pay.dto.transfer.PayTransferUnifiedDto;
import vip.liux.contracts.models.pay.PayTransfer;
import vip.liux.contracts.repositories.pay.PayTransferRepository;
import vip.liux.contracts.shared.enums.pay.PayNotifyType;
import vip.liux.contracts.shared.enums.pay.PayTransferStatus;
import vip.liux.contracts.shared.kits.ApplicationKit;
import vip.liux.core.utils.JsonUtils;

import java.util.List;
import java.util.Objects;

import static vip.liux.application.PageKit.toPage;
import static vip.liux.application.PageKit.toPaged;
import static vip.liux.contracts.shared.enums.ErrorCode.*;
import static vip.liux.contracts.shared.util.ServiceExceptionUtil.exception;


/**
 * 转账 Service 实现类
 *
 * @author jason
 */
@Service
@Slf4j
public class PayTransferServiceImpl implements PayTransferService {

    @Resource
    private PayTransferRepository transferMapper;
    @Resource
    private PayAppService appService;
    @Resource
    private PayChannelService channelService;
    @Resource
    private PayNotifyService notifyService;
    @Resource
    private BizCodeGenerator bizCodeGenerator;

    @Override
    public PayTransferDto createTransfer(PayTransferCreateDto input) {
        // 1.1 校验 App
        PayAppDto payApp = appService.validPayApp(input.getAppKey());
        // 1.2 校验支付渠道是否有效
        PayChannelDto channel = channelService.validPayChannel(payApp.getId(), input.getChannelCode());
        PayClient<?> client = channelService.getPayClient(channel.getId());
        if (client == null) {
            log.error("[createTransfer][渠道编号({}) 找不到对应的支付客户端]", channel.getId());
            throw exception(CHANNEL_NOT_FOUND);
        }
        // 1.3 校验转账单已经发起过转账
        PayTransfer transfer = validateTransferCanCreate(input, payApp.getId());

        // 2.1 情况一：不存在创建转账单，则进行创建
        if (transfer == null) {
            String no = bizCodeGenerator.generateTransferNo();
            transfer = AutoMapperProfile.INSTANCE.convert(input)
                    .setAppId(channel.getAppId()).setChannelId(channel.getId())
                    .setTransferNo(no).setStatus(PayTransferStatus.WAITING)
                    .setNotifyUrl(payApp.getTransferNotifyUrl());
            transferMapper.save(transfer);
        } else {
            // 2.2 情况二：存在创建转账单，但是状态为关闭，则更新为等待中
            transferMapper.updateByIdAndStatus(transfer.getId(), transfer.getStatus(), PayTransferStatus.WAITING);
        }
        ClientPayTransferDto unifiedTransferResp = null;
        try {
            // 3. 调用三方渠道发起转账
            PayTransferUnifiedDto transferUnifiedReq = AutoMapperProfile.INSTANCE.convertToClient(input)
                    .setOutTransferNo(transfer.getTransferNo())
                    .setNotifyUrl(genChannelTransferNotifyUrl(channel));
            unifiedTransferResp = client.unifiedTransfer(transferUnifiedReq);
            // 4. 通知转账结果
            getSelf().notifyTransfer(channel, unifiedTransferResp);
        } catch (Throwable e) {
            // 注意这里仅打印异常，不进行抛出。
            // 原因是：虽然调用支付渠道进行转账发生异常（网络请求超时），实际转账成功。这个结果，后续转账轮询可以拿到。
            //       或者，使用相同 no 再次发起转账请求
            log.error("[createTransfer][转账编号({}) requestDTO({}) 发生异常]", transfer.getId(), input, e);
        }
        return new PayTransferDto().setTransferNo(transfer.getTransferNo())
                .setChannelPackageInfo(unifiedTransferResp != null ? unifiedTransferResp.getChannelPackageInfo() : null);
    }

    /**
     * 根据支付渠道的编码，生成支付渠道的回调地址
     *
     * @param channel 支付渠道
     * @return 支付渠道的回调地址  配置地址 + "/" + channel id
     */
    private String genChannelTransferNotifyUrl(PayChannelDto channel) {
        return "http://localhost:9010/api/backend" + "/" + channel.getId();
    }

    private PayTransfer validateTransferCanCreate(PayTransferCreateDto input, Long appId) {
        PayTransfer transfer = transferMapper.findByAppIdAndMerchantTransferId(appId, input.getMerchantTransferId());
        if (transfer != null) {
            // 只有转账单状态为关闭，才能再次发起转账
            if (!PayTransferStatus.isClosed(transfer.getStatus())) {
                throw exception(PAY_TRANSFER_CREATE_FAIL_STATUS_NOT_CLOSED);
            }
            // 校验参数是否一致
            if (ObjectUtils.notEqual(input.getPrice(), transfer.getPrice())) {
                throw exception(PAY_TRANSFER_CREATE_PRICE_NOT_MATCH);
            }
            if (ObjectUtils.notEqual(input.getChannelCode(), transfer.getChannelCode())) {
                throw exception(PAY_TRANSFER_CREATE_CHANNEL_NOT_MATCH);
            }
        }
        // 如果状态为等待状态：不知道渠道转账是否发起成功
        // 特殊：允许使用相同的 no 再次发起转账，渠道会保证幂等
        return transfer;
    }

    @Transactional(rollbackFor = Exception.class)
    // 注意，如果是方法内调用该方法，需要通过 getSelf().notifyTransfer(channel, notify) 调用，否则事务不生效
    public void notifyTransfer(PayChannelDto channel, ClientPayTransferDto notify) {
        // 转账成功的回调
        if (PayTransferStatus.isSuccess(notify.getStatus())) {
            notifyTransferSuccess(channel, notify);
        }
        // 转账关闭的回调
        if (PayTransferStatus.isClosed(notify.getStatus())) {
            notifyTransferClosed(channel, notify);
        }
        // 转账处理中的回调
        if (PayTransferStatus.isProcessing(notify.getStatus())) {
            notifyTransferProgressing(channel, notify);
        }
        // WAITING 状态无需处理
    }

    private void notifyTransferProgressing(PayChannelDto channel, ClientPayTransferDto notify) {
        // 1. 校验
        PayTransfer transfer = transferMapper.findByAppIdAndTransferNo(channel.getAppId(), notify.getOutTransferNo());
        if (transfer == null) {
            throw exception(PAY_TRANSFER_NOT_FOUND);
        }
        if (PayTransferStatus.isProcessing(transfer.getStatus())) { // 如果已经是转账中，直接返回，不用重复更新
            log.info("[notifyTransferProgressing][transfer({}) 已经是转账中状态，无需更新]", transfer.getId());
            return;
        }
        if (!PayTransferStatus.isWaiting(transfer.getStatus())) {
            throw exception(PAY_TRANSFER_NOTIFY_FAIL_STATUS_IS_NOT_WAITING);
        }

        // 2. 更新状态
        int updateCounts = transferMapper.updateByIdAndStatus(transfer.getId(),
                PayTransferStatus.WAITING, PayTransferStatus.PROCESSING, notify.getChannelPackageInfo());
        if (updateCounts == 0) {
            throw exception(PAY_TRANSFER_NOTIFY_FAIL_STATUS_IS_NOT_WAITING);
        }
        log.info("[notifyTransferProgressing][transfer({}) 更新为转账进行中状态]", transfer.getId());
    }

    private void notifyTransferSuccess(PayChannelDto channel, ClientPayTransferDto notify) {
        // 1. 校验状态
        PayTransfer transfer = transferMapper.findByAppIdAndTransferNo(channel.getAppId(), notify.getOutTransferNo());
        if (transfer == null) {
            throw exception(PAY_TRANSFER_NOT_FOUND);
        }
        if (PayTransferStatus.isSuccess(transfer.getStatus())) { // 如果已成功，直接返回，不用重复更新
            log.info("[notifyTransferSuccess][transfer({}) 已经是成功状态，无需更新]", transfer.getId());
            return;
        }
        if (!PayTransferStatus.isWaitingOrProcessing(transfer.getStatus())) {
            throw exception(PAY_TRANSFER_NOTIFY_FAIL_STATUS_NOT_WAITING_OR_PROCESSING);
        }

        // 2. 更新状态
        int updateCounts = transferMapper.updateByIdAndStatus(transfer.getId(),
                List.of(PayTransferStatus.WAITING, PayTransferStatus.PROCESSING),
                PayTransferStatus.SUCCESS,
                notify.getSuccessTime(),
                notify.getChannelTransferNo(),
                JsonUtils.toJson(notify));
        if (updateCounts == 0) {
            throw exception(PAY_TRANSFER_NOTIFY_FAIL_STATUS_NOT_WAITING_OR_PROCESSING);
        }
        log.info("[notifyTransferSuccess][transfer({}) 更新为已转账]", transfer.getId());

        // 3. 插入转账通知记录
        notifyService.createPayNotifyTask(PayNotifyType.TRANSFER, transfer.getTransferNo());
    }

    private void notifyTransferClosed(PayChannelDto channel, ClientPayTransferDto notify) {
        // 1. 校验状态
        PayTransfer transfer = transferMapper.findByAppIdAndTransferNo(channel.getAppId(), notify.getOutTransferNo());
        if (transfer == null) {
            throw exception(PAY_TRANSFER_NOT_FOUND);
        }
        if (PayTransferStatus.isClosed(transfer.getStatus())) { // 如果已是关闭状态，直接返回，不用重复更新
            log.info("[notifyTransferClosed][transfer({}) 已经是关闭状态，无需更新]", transfer.getId());
            return;
        }
        if (!PayTransferStatus.isWaitingOrProcessing(transfer.getStatus())) {
            throw exception(PAY_TRANSFER_NOTIFY_FAIL_STATUS_NOT_WAITING_OR_PROCESSING);
        }

        // 2. 更新状态
        int updateCount = transferMapper.updateByIdAndStatus(transfer.getId(),
                List.of(PayTransferStatus.WAITING, PayTransferStatus.PROCESSING),
                PayTransferStatus.CLOSED,
                notify.getChannelTransferNo(),
                JsonUtils.toJson(notify),
                notify.getChannelErrorCode(),
                notify.getChannelErrorMsg());
        if (updateCount == 0) {
            throw exception(PAY_TRANSFER_NOTIFY_FAIL_STATUS_NOT_WAITING_OR_PROCESSING);
        }
        log.info("[notifyTransferClosed][transfer({}) 更新为关闭状态]", transfer.getId());

        // 3. 插入转账通知记录
        notifyService.createPayNotifyTask(PayNotifyType.TRANSFER, transfer.getTransferNo());
    }

    @Override
    public PayTransferDto getTransfer(Long id) {
        return AutoMapperProfile.INSTANCE.convert(transferMapper.findById(id).orElseThrow(() -> exception(PAY_TRANSFER_NOT_FOUND)));
    }

    @Override
    public PayTransferDto getTransferByNo(String transferNo) {
        return AutoMapperProfile.INSTANCE.convert(transferMapper.findByTransferNo(transferNo));
    }

    @Override
    public PagedResultDto<PayTransferDto> getTransferPage(PayTransferQueryDto input) {
        return toPaged(transferMapper.findPage(
                input.getTransferNo(),
                input.getAppId(),
                input.getChannelCode(),
                input.getMerchantTransferId(),
                input.getStatus(),
                input.getUserName(),
                input.getUserAccount(),
                input.getChannelTransferNo(),
                input.getStart(),
                input.getEnd(),
                toPage(input)
        ).map(AutoMapperProfile.INSTANCE::convert));
    }

    @Override
    public int syncTransfer() {
        List<PayTransfer> list = transferMapper.findByStatusIn(List.of(PayTransferStatus.WAITING, PayTransferStatus.PROCESSING));
        if (CollectionUtils.isEmpty(list)) {
            return 0;
        }
        int count = 0;
        for (PayTransfer transfer : list) {
            if (!Objects.equals(transfer.getId(), 54L)) {
                continue;
            }
            count += syncTransfer(transfer) ? 1 : 0;
        }
        return count;
    }

    @Override
    public void syncTransfer(Long id) {
        PayTransfer transfer = transferMapper.findById(id).orElseThrow(() -> exception(PAY_TRANSFER_NOT_FOUND));
        syncTransfer(transfer);
    }

    private boolean syncTransfer(PayTransfer transfer) {
        try {
            // 1. 查询转账订单信息
            PayClient<?> payClient = channelService.getPayClient(transfer.getChannelId());
            if (payClient == null) {
                log.error("[syncTransfer][渠道编号({}) 找不到对应的支付客户端]", transfer.getChannelId());
                return false;
            }
            ClientPayTransferDto resp = payClient.getTransfer(transfer.getTransferNo());

            // 2. 回调转账结果
            notifyTransfer(transfer.getChannelId(), resp);
            return true;
        } catch (Throwable ex) {
            log.error("[syncTransfer][transfer({}) 同步转账单状态异常]", transfer.getId(), ex);
            return false;
        }
    }

    public void notifyTransfer(Long channelId, ClientPayTransferDto notify) {
        // 校验渠道是否有效
        PayChannelDto channel = channelService.validPayChannel(channelId);
        // 通知转账结果给对应的业务
        getSelf().notifyTransfer(channel, notify);
    }

    /**
     * 获得自身的代理对象，解决 AOP 生效问题
     *
     * @return 自己
     */
    private PayTransferServiceImpl getSelf() {
        return ApplicationKit.getBean(getClass());
    }
}
